const PLAYER_NAMES = ['甲', '乙', '丙', '丁']

const AI_DELAY = 100

const PHASE_DRAW = 0
const PHASE_CONCEAL = 1
const PHASE_DISCARD = 2
const PHASE_MELD = 3

const CHOICE_NONE = 0
const CHOICE_DISCARD = 1
const CHOICE_ADDED_KONG = 2
const CHOICE_CONCEALED_PAIR = 3
const CHOICE_CONCEALED_CHOW = 4
const CHOICE_CONCEALED_PUNG = 5
const CHOICE_CONCEALED_KONG = 6
const CHOICE_CHOW = 7
const CHOICE_PUNG = 8
const CHOICE_KONG = 9
const CHOICE_HU = 10
const CHOICE_TSUMO = 11
const CHOICE_RIICHI = 12
const CHOICE_RYUUKYOKU = 13

const TEXT_CHOICE = ['跳过', '出牌', '加杠', '暗对', '暗顺', '暗刻', '暗杠', '吃', '碰', '杠', '荣', '自摸', '立直', '流局']

const SUIT_NAME = 'mpsz'

const TILE_M0 = 0 << 2 | 0
const TILE_M1 = 1 << 2 | 0
const TILE_M2 = 2 << 2 | 0
const TILE_M3 = 3 << 2 | 0
const TILE_M4 = 4 << 2 | 0
const TILE_M5 = 5 << 2 | 0
const TILE_M6 = 6 << 2 | 0
const TILE_M7 = 7 << 2 | 0
const TILE_M8 = 8 << 2 | 0
const TILE_M9 = 9 << 2 | 0

const TILE_P0 = 0 << 2 | 1
const TILE_P1 = 1 << 2 | 1
const TILE_P2 = 2 << 2 | 1
const TILE_P3 = 3 << 2 | 1
const TILE_P4 = 4 << 2 | 1
const TILE_P5 = 5 << 2 | 1
const TILE_P6 = 6 << 2 | 1
const TILE_P7 = 7 << 2 | 1
const TILE_P8 = 8 << 2 | 1
const TILE_P9 = 9 << 2 | 1

const TILE_S0 = 0 << 2 | 2
const TILE_S1 = 1 << 2 | 2
const TILE_S2 = 2 << 2 | 2
const TILE_S3 = 3 << 2 | 2
const TILE_S4 = 4 << 2 | 2
const TILE_S5 = 5 << 2 | 2
const TILE_S6 = 6 << 2 | 2
const TILE_S7 = 7 << 2 | 2
const TILE_S8 = 8 << 2 | 2
const TILE_S9 = 9 << 2 | 2

const TILE_Z0 = 0 << 2 | 3
const TILE_Z1 = 1 << 2 | 3
const TILE_Z2 = 2 << 2 | 3
const TILE_Z3 = 3 << 2 | 3
const TILE_Z4 = 4 << 2 | 3
const TILE_Z5 = 5 << 2 | 3
const TILE_Z6 = 6 << 2 | 3
const TILE_Z7 = 7 << 2 | 3

const KANJI_RANK = ['〇', '一', '二', '三', '四', '伍', '六', '七', '八', '九']
const KANJI_SUIT = ['萬', '饼', '索']
const KANJI_ZI = ['风', '东', '南', '西', '北', '白', '发', '中']
const KANJI_AKA = '赤'

const TILE_AKA = 1 << 6

const TILE_YAOCHUU = [
	TILE_Z1, TILE_Z2, TILE_Z3, TILE_Z4, TILE_Z5, TILE_Z6, TILE_Z7,
	TILE_M1, TILE_M9, TILE_P1, TILE_P9, TILE_S1, TILE_S9,
]
const TILE_WINDS = TILE_YAOCHUU

const FURITEN_PERMANENT = 1
const FURITEN_TEMPORARY = 2

function getTileValue(tile) {
	return tile & 63
}

function getTileKanjiText(tile) {
	return ((tile & TILE_AKA) ? KANJI_AKA : '') + ((tile & 3) === 3 ? KANJI_ZI[(tile & 63) >> 2] : (KANJI_RANK[(tile & 63) >> 2] + KANJI_SUIT[tile & 3]))
}

function getTileText(tile) {
	return ((tile & 63) >> 2) + SUIT_NAME[tile & 3] + ((tile & TILE_AKA) ? '*' : '')
}

function countYaochuu(tiles) {
	let count = 0, values = tiles.map(getTileValue)
	for (const yaochuu in TILE_YAOCHUU) if (values.indexOf(yaochuu) !== -1) {
		++count
	}
	return count
}

function compareTiles(a, b) {
	return (a & 3) === (b & 3) ? ((a & 63) - (b & 63)) : ((a & 3) - (b & 3))
}

function createTilesFromString(string) {
	let nums = [], tiles = []
	for (const char of string) {
		const num = (char.charCodeAt(0) ^ 48)
		if (num >= 0 && num <= 9) {
			nums.push(num)
		} else {
			const suit = SUIT_NAME.indexOf(char)
			if (suit >= 0 && suit <= 3) {
				for (const num of nums) {
					tiles.push(num << 2 | suit)
				}
				nums = []
			}
		}
	}
	return tiles
}

function stringifyHu(hu, isDealer) {
	if (!hu)
		return '未和'
	let huString = `${(hu.tsumo ? '自摸' : '荣')}${hu.points * (isDealer ? 1.5 : 1)}点`
	if (hu.fan && hu.fan.length)
		huString += `（${hu.fan.map(fan => `${FAN_NAME[fan[0]]}${fan[1]}${fan[2] ? '.5' : ''}`).join('，')}）`
	return huString
}

function stringifyDealIn(dealIn) {
	return (dealIn[1] ? '分付' : '放铳') + `${dealIn[0]}点`
}

function newTileCounter() {
	return new Array(39).fill(4)
}

function countTileArray(counter, array) {
	for (const tile of array) {
		--counter[getTileValue(tile)]
	}
}

function countTileMelds(counter, melds) {
	for (const meld of melds) {
		if (meld[2]) {
			--counter[getTileValue(meld[2])]
		}
		for (const tile of meld[3]) {
			--counter[getTileValue(tile)]
		}
	}
}

function reverseArray(array) {
	for (let i = 0, j = array.length - 1; i < j; ++i, --j) {
		let tmp = array[i]
		array[i] = array[j]
		array[j] = tmp
	}
}

function shuffleArray(array) {
	for (let i = array.length - 1; i > 0; --i) {
		let j = Math.floor(Math.random() * (i + 1))
		let tmp = array[i]
		array[i] = array[j]
		array[j] = tmp
	}
}

function removeElementFromArray(array, element) {
	array.splice(array.indexOf(element), 1)
}

function sortAndUnique(array) {
	if (!array.length) {
		return []
	}
	let sortedArray = structuredClone(array)
	sortedArray.sort((a, b) => a - b)
	let uniquedArray = []
	for (const element of sortedArray) {
		if (!uniquedArray.length || uniquedArray[uniquedArray.length - 1] < element) {
			uniquedArray.push(element)
		}
	}
	return uniquedArray
}

function searchSetFromArray(array, callbacks) {
	let results = [], result = new Array(callbacks.length)
	function dfs(level, i) {
		if (level < 0) {
			results.push(structuredClone(result))
		} else {
			const callback = callbacks[level]
			for (; i--;)if (callback(array[i])) {
				result[level] = array[i]
				dfs(level - 1, i)
			}
		}
	}
	dfs(callbacks.length - 1, array.length)
	return sortAndUnique(results)
}

function getConfig(id) {
	const element = document.getElementById(id)
	const type = element.type
	const value = (type === 'checkbox' ? element.checked : element.valueAsNumber)
	console.log(`Read config ${id} = (${typeof value})${value}`)
	return value
}